HW Exam DevOps¶
Зорин Дмитрий Игоревич
Т.к. описание задания и критериев расходились по требованиям, то работа была выполнена в 2 этапа:
- Выполнено задание в ноутбуке с ADR и нагрузочным тестированием.
- Создан отдельный проект с микросервисом, API, ADR, MLflow, Terraform (YC), OpenTofu, Github Actions, YC serverless container, Locust report и выводами.
Описание¶
Критерий 1. Проектирование сервиса¶
Сервис реализован на FastAPI, эндпоинты заданы контрактами Pydantic:
- GET /health (быстрый healthcheck),
- GET /healthz (health с проверкой загрузки модели),
- POST /predict (тело IrisFeatures с 4 признаками, ответ — predicted_class и probabilities).
Эндпоинты описаны в app/main.py, Swagger /docs доступен из контейнера; модель берётся из models/rf_iris_model.pkl, путь задаётся через MODEL_PATH.
Критерий 2. Анализ подходов к разделению монолита¶
Логические компоненты в одном репозитории:
- API-сервис (FastAPI) в Docker-образе — принимает health/predict.
- ML-пайплайн (src/train.py) — обучает RandomForest на Iris, сохраняет модель и отчёты (MLflow, Evidently, Deepchecks).
- Инфраструктура — Docker/Docker Compose для локального запуска и Terraform для serverless-контейнера YC; переменные вынесены в tfvars.
- Тестирование — Locust (locustfile.py) бьёт по /health и /predict.
- Связи: пайплайн генерирует модель → API её грузит; Terraform/Compose разворачивают API; Locust нагрузочно тестирует API.
Критерий 3. Программное создание инфраструктуры¶
Использован Terraform: конфиги в terraform/*.tf поднимают Yandex Container Registry, репозиторий и serverless container с образом fastapi-sls:v3, переменная окружения PORT=8080 задаётся в блоке image. Переменные описаны в variables.tf, пример значений — terraform.tfvars.example; запуск: terraform init, terraform apply (tfvars с секретами держатся локально). Это обеспечивает развёртывание инфраструктуры с нуля.
Критерий 4. ML-пайплайн¶
Отчёт Evidently по train/test (Iris) показал отсутствие дрейфа: 0 из 5 колонок детектированы как дрейфующие, p-value для числовых признаков > 0.5, категориальный target без новых/пропавших категорий. Вывод: текущий тестовый срез статистически совпадает с обучающим, значимых сдвигов распределений нет; модель может работать стабильно для этой выборки.
Критерий 5. Принятие архитектурных решений¶
ADR файл создан. Выбран контейнеризированный FastAPI сервис с отдельным ML‑пайплайном и IaC (Terraform/Compose), потому что он обеспечивает воспроизводимость, чёткие контракты и переносимость между локальной средой, CI и облаком, несмотря на более высокую начальную сложность и необходимость управлять секретами.
Критерий 6. Итоговое оформление¶
Сервис отвечает корректно, но при такой нагрузке упирается в лимиты serverless. Для снижения 429 можно либо уменьшить rps, либо увеличить ресурсы/параметры concurrency/память, либо прогревать.
ADR¶
Local test¶
Mlflow¶
poetry run python src/train.py
API¶
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000
Evidently¶
from IPython.display import IFrame, HTML
from pathlib import Path
nb_dir = Path.cwd()
report_path = (nb_dir / ".." / "reports" / "evidently_data_drift.html").resolve()
HTML(report_path.read_text(encoding="utf-8"))
Loading...
The Kernel crashed while executing code in the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details.
Docker¶
docker compose up --build -d
curl http://localhost:8000/health
StatusCode : 200
StatusDescription : OK
Content : {"status":"ok"}
RawContent : HTTP/1.1 200 OK
Content-Length: 15
Content-Type: application/json
Date: Sat, 29 Nov 2025 19:45:53 GMT
Server: uvicorn
{"status":"ok"}
Forms : {}
Headers : {[Content-Length, 15], [Content-Type, application/json], [Date, Sat, 29 Nov 2025 19:45:53 GMT], [Server, uvicorn]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 15
poetry run locust -f locustfile.py --headless --host=http://localhost:8000 -u 20 -r 2 -t 1m --html=reports/locust_report.html
from IPython.display import IFrame, HTML
from pathlib import Path
nb_dir = Path.cwd()
report_path = (nb_dir / ".." / "reports" / "locust_report.html").resolve()
HTML(report_path.read_text(encoding="utf-8"))
Tofu¶
cd tofu
tofu init
tofu validate
Github¶
Cloud: Yandex Container Registry¶
init¶
Авторизация и настройка Yandex Cloud
yc init
yc container registry configure-docker
Terraform¶
# перезаписать образ и пушить (т.к. использовался в HW4 с тегом v1)
docker build -t cr.yandex/crplljbgcj6qsfgsn2ln/fastapi-sls:v2 .
docker push cr.yandex/crplljbgcj6qsfgsn2ln/fastapi-sls:v2
cd terraform
terraform init
# Применение изменений
terraform apply
terraform plan
# Проверка работы
yc serverless container revision list --container-id <id>
# проверка health
curl.exe -v https://bbasqrbl76vu9ukarbc4.containers.yandexcloud.net/healthz
Locust¶
poetry run locust -f locustfile.py --headless --host=https://bbasqrbl76vu9ukarbc4.containers.yandexcloud.net -u 20 -r 2 -t 1m --html=reports/locust_report_cloud.html
from IPython.display import IFrame, HTML
from pathlib import Path
nb_dir = Path.cwd()
report_path = (nb_dir / ".." / "reports" / "locust_report_cloud.html").resolve()
HTML(report_path.read_text(encoding="utf-8"))
from IPython.display import IFrame, HTML
from pathlib import Path
nb_dir = Path.cwd()
report_path = (nb_dir / ".." / "reports" / "exam_{Zorin}_test_report_100_10_60.html").resolve()
HTML(report_path.read_text(encoding="utf-8"))
Выводы¶
- Локально сервис очень быстрый: /health среднее ~6 мс, P95 ~15 мс; /predict среднее ~112 мс, P95 ~130 мс, хвост до ~1.4 с. RPS ~9.4, ошибок 0%.
- В облаке задержки выше из‑за сетевых и холодных стартов: /health среднее ~211 мс (P95 ~650 мс, хвост до 5 s), /predict среднее ~280 мс (P95 ~700 мс, хвост до 7.7 s). RPS ~8.3, ошибок 0%.
- Соотношение 75% health / 25% predict по сценарию, ошибок нет = стабильность под нагрузкой подтверждена.
- Хвосты (P99) до нескольких секунд в облаке: холодные старты + ограниченные ресурсы (256 MB). Можно улучшить, увеличив память или прогрев.
- При 100 юзерах/10 rps спавне (~40 rps суммарно) cloud: P50 ~180–190 мс, P95 ~0.5–0.6 с, хвосты до 9–12 с; 1.67 % 429 (Too Many Requests) на /health и /predict — лимиты serverless.
Вывод: Сервис отвечает корректно, но при такой нагрузке упирается в лимиты serverless. Для снижения 429 можно либо уменьшить rps, либо увеличить ресурсы/параметры concurrency/память, либо прогревать.